using System;
using System.Collections;
using System.Collections.Generic;
using System.Html;
using System.Runtime.CompilerServices;
using Awaiter;
using jQueryApi;

namespace Nsb.Classes
{
    [IgnoreNamespace]
    public partial class DomElement
    {
        public const string ResultRx = "result";

        private const int SlideDown = 250;
        private static bool _efpCheck;
        private static bool _efpRelative;
        private static readonly bool OthrFocsCrtd = CreateOtherFocus();

        private static int _idCnt;
        private static readonly int IdTime = (new Date()).GetTime();
        private static jQueryObject _otherFocusEl;
        public jQueryObject Element;

        public static string LclId
        {
            get { return IdTime + "x" + (++_idCnt); }
        }

        public static void Trigger(string nm, object arg)
        {
            object[] args = new object[1];
            args[0] = arg;
            jQuery.Document.Trigger(nm, args);
        }

        public void FocusItem(string selector)
        {
            jQuery.Select(selector, Element).Focus();
        }

        public void ClearElement()
        {
            if (Script.IsValue(Element))
                Element.Empty();
        }

        public static bool IsValidName(string name)
        {
            return !String.IsNullOrEmpty(CleanName(name));
        }

        public static bool IsValidNames(string name1, string name2)
        {
            return IsValidName(name1) && IsValidName(name2);
        }

        public static void AddStyle(string style)
        {
            jQuery.FromHtml("<style type='text/css'/>").Append(style).AppendTo("head");
        }

        public static string CleanName(string name)
        {
            return Script.IsNullOrUndefined(name) ? "" : name.Trim();
        }

        public static string OuterHtml(jQueryObject el)
        {
            if ((bool) Script.Literal("{0}[0].hasOwnProperty('outerHTML')", el))
                return (string) Script.Literal("{0}[0].outerHTML", el);
            jQueryObject tmp = jQuery.FromHtml("<div/>").InsertAfter(el);
            el.AppendTo(tmp);
            string html = tmp[0].InnerHTML;
            el.InsertBefore(tmp);
            tmp.Remove();
            return html;
        }

        public static void CancelEvent(jQueryEvent e)
        {
            if (e == null) return;
            e.StopImmediatePropagation();
            e.StopPropagation();
            e.PreventDefault();
        }

        public static void FocusElementAw(Await awp, jQueryObject el)
        {
            el.Focus();
            awp.Done();
        }

        [AlternateSignature]
        public extern void ScrollToAw(Await awp);

        public void ScrollToAw(Await awp, jQueryObject el)
        {
            el = Script.Value(el, Element);
            ScrollToPosAw(awp, el.GetOffset().Top - 2);
        }

        public static void ScrollToPosAw(Await awp, int y)
        {
            bool once = false; // Fix for that animate is run 3 times on body, html, and document
            jQuery.Select("body,html,document").Animate(
                new Dictionary("scrollTop", y),
                300,
                EffectEasing.Swing,
                delegate
                {
                    if (!once)
                    {
                        once = true;
                        awp.Done();
                    }
                }
                );
        }

        public void ScrollInViewAw(Await awp, jQueryObject el)
        {
            if (el == null)
            {
                awp.Done();
                return;
            }
            int ch = WindowHeight();
            int wb = ch + DocumentScrollTop();
            int eb = el.GetOffset().Top + el.GetOuterHeight();
            if (eb > wb)
                ScrollToPosAw(awp, eb - ch + 5);
            else
                awp.Done();
        }

        public void RemoveAw(Await awp)
        {
            Element.Remove();
            awp.Done();
        }

        [AlternateSignature]
        public extern void FadeOutAw(Await awp);

        public void FadeOutAw(Await awp, int ms)
        {
            ms = Script.Value(ms, SlideDown);
            jQueryObject jar = jQuery.Select(".Jar", Element);
            jar = jar.Length > 0 ? jar : Element;
            jar.FadeOut(ms, delegate { awp.Done(); });
        }

        [AlternateSignature]
        public extern void FadeInAw(Await awp);

        public void FadeInAw(Await awp, int ms)
        {
            ms = Script.Value(ms, SlideDown);
            jQueryObject jar = jQuery.Select(".Jar", Element);
            jar = jar.Length > 0 ? jar : Element;
            jar.FadeIn(ms, delegate { awp.Done(); });
        }

        [AlternateSignature]
        public extern void SlideDownAw(Await awp);

        public void SlideDownAw(Await awp, int ms)
        {
            ms = Script.Value(ms, SlideDown);
            jQueryObject jar = jQuery.Select(".Jar", Element);
            jar = jar.Length > 0 ? jar : Element;
            jar.SlideDown(ms, delegate { awp.Done(); });
        }

        [AlternateSignature]
        public extern void SlideUpAw(Await awp);

        public void SlideUpAw(Await awp, int ms)
        {
            ms = Script.Value(ms, SlideDown);
            jQueryObject jar = jQuery.Select(".Jar", Element);
            jar = jar.Length > 0 ? jar : Element;
            jar.SlideUp(ms, delegate { awp.Done(); });
        }

        public void HideJar()
        {
            jQueryObject jar = jQuery.Select(".Jar", Element);
            jar = jar.Length > 0 ? jar : Element;
            jar.Hide();
        }

        public void ShowJar()
        {
            jQueryObject jar = jQuery.Select(".Jar", Element);
            jar = jar.Length > 0 ? jar : Element;
            jar.Show();
        }

        [AlternateSignature]
        public extern void Focus();

        public void Focus(jQueryObject el)
        {
            el = Script.Value(el, Element);
            Window.SetTimeout(delegate { el.Focus(); }, 1);
        }

        public static TargetEvent CalcTargets(jQueryEvent e)
        {
            TargetEvent te = (TargetEvent) (object) e;
            jQueryObject t = jQuery.FromElement(e.Target);
            jQueryPosition o = t.GetOffset();
            te.OffsetX = e.PageX - o.Left;
            te.OffsetY = e.PageY - o.Top;
            te.TargetWidth = t.GetWidth();
            te.TargetHeight = t.GetHeight();
            te.TargetX = e.OffsetX/te.TargetWidth;
            te.TargetY = e.OffsetY/te.TargetHeight;
            te.TargetOffset = o;
            return te;
        }

        public static bool HasClass(string all, string has)
        {
            //return (((RegularExpression)Script.Literal(@"/\b{0}\b/", has)).Test(all)) ;
            return (RegMe(@"/\b" + has + @"\b/").Test(all));
        }

        public static void Unselectable(jQueryObject el)
        {
            Element elem = el[0];
            Type.SetField(elem, "onselectstart", Script.Literal("function() { return false; }"));
            Type.SetField(elem.Style, "MozUserSelect", "none");
            Type.SetField(elem.Style, "KhtmlUserSelect", "none");
            Type.SetField(elem, "unselectable", "on");
        }

        public static int WindowWidth()
        {
            return FFilterResults(
                Script.IsValue(Window.Self.InnerWidth) ? Window.Self.InnerWidth : 0,
                Script.IsValue(Document.DocumentElement) ? Document.DocumentElement.ClientWidth : 0,
                Script.IsValue(Document.Body) ? Document.Body.ClientWidth : 0
                );
        }

        public static int WindowHeight()
        {
            return FFilterResults(
                Script.IsValue(Window.Self.InnerHeight) ? Window.Self.InnerHeight : 0,
                Script.IsValue(Document.DocumentElement) ? Document.DocumentElement.ClientHeight : 0,
                Script.IsValue(Document.Body) ? Document.Body.ClientHeight : 0
                );
        }

        public static int DocumentWidth()
        {
            return jQuery.FromObject(jQuery.Document).GetWidth();
        }

        public static int DocumentHeight()
        {
            return jQuery.FromObject(jQuery.Document).GetHeight();
        }

        public static int[] GetCodeDim(string html)
        {
            jQueryObject el = jQuery.FromHtml(html).AppendTo(Document.Body);
            int[] wh = (int[]) Script.Literal("[{0},{1}]", el.GetOuterWidth(), el.GetOuterHeight());
            el.Remove();
            return wh;
        }

        public static int DocumentScrollLeft()
        {
            return FFilterResults(
                Script.IsValue(Window.Self.PageXOffset) ? Window.Self.PageXOffset : 0,
                Script.IsValue(Document.DocumentElement) ? Document.DocumentElement.ScrollLeft : 0,
                Script.IsValue(Document.Body) ? Document.Body.ScrollLeft : 0
                );
        }

        public static int DocumentScrollTop()
        {
            return FFilterResults(
                Script.IsValue(Window.Self.PageYOffset) ? Window.Self.PageYOffset : 0,
                Script.IsValue(Document.DocumentElement) ? Document.DocumentElement.ScrollTop : 0,
                Script.IsValue(Document.Body) ? Document.Body.ScrollTop : 0
                );
        }

        private static int FFilterResults(int nWin, int nDocel, int nBody)
        {
            Script.Literal("var n_result = {0} ? {0} : 0; if ({1} && (!n_result || (n_result > {1}))) n_result = {1};", nWin, nDocel);
            return (int) Script.Literal("{0} && (!n_result || (n_result > {0})) ? {0} : n_result;", nBody);
        }

        public static Element ElementFromPoint(int x, int y)
        {
            //Script.Literal("debugger");
            if (!(bool) Script.Literal("document.elementFromPoint"))
                return null;

            if (!_efpCheck)
            {
                int sl = 0;
                if ((bool) Script.Literal("({0} = jQuery(document).scrollTop()) > 0", sl))
                {
                    _efpRelative = (bool) Script.Literal("document.elementFromPoint(0, {0} + jQuery(window).height() -1) == null", sl);
                }
                else if ((bool) Script.Literal("({0} = jQuery(document).scrollLeft()) > 0", sl))
                {
                    _efpRelative = (bool) Script.Literal("document.elementFromPoint({0} + jQuery(window).width() -1, 0) == null", sl);
                }
                _efpCheck = (sl > 0);
            }

            if (_efpRelative)
            {
                x -= jQuery.FromObject(Window.Document).GetScrollLeft();
                y -= jQuery.FromObject(Window.Document).GetScrollTop();
            }
            return Document.ElementFromPoint(x, y);
        }

        [AlternateSignature]
        public extern void EscapeKeyAction();

        public void EscapeKeyAction(jQueryEventHandler fn)
        {
            AddKeyEvent(27, fn);
        }

        [AlternateSignature]
        public extern void AddKeyEvent(int keyCode, jQueryEventHandler fn);

        public void AddKeyEvent(int keyCode, Action<jQueryEvent> fn, Dictionary qualifiers)
        {
            qualifiers = Script.Value(qualifiers, new Dictionary());
            string token = "KeyToken" + keyCode;
            if (fn == null)
            {
                RemKeyEvent((string) Type.GetField(this, token));
                return;
            }
            string kd = "keydown." + LclId;

            jQueryEventHandler del =
                delegate(jQueryEvent e)
                {
                    if (e.Which == keyCode && ObjectsHave(qualifiers, e))
                    {
                        CancelEvent(e);
                        fn(e);
                    }
                }
                ;

            jQuery.FromObject(jQuery.Document).Bind(kd, del);
            Type.SetField(this, token, kd);
        }

        public bool ObjectsHave(object o1, object o2)
        {
            Dictionary a = (Dictionary) o1;
            Dictionary b = (Dictionary) o2;
            foreach (KeyValuePair<string, object> p in a)
            {
                if (a[p.Key] != b[p.Key])
                    return false;
            }
            return true;
        }

        public void RemKeyEvent(string eventKey)
        {
            jQuery.FromObject(jQuery.Document).Unbind(eventKey);
        }

        public static RegularExpression RegMe(string rx)
        {
            return (RegularExpression) Script.Eval(rx);
        }

        [AlternateSignature]
        public static extern void RequireResultAw(Await awp, AwaitBoolDelegateObject fn);

        [AlternateSignature]
        public static extern void RequireResultAw(Await awp, bool required);

        public static void RequireResultAw(Await awp, object required)
        {
            if (jQuery.IsFunction(required))
            {
                AwaitBoolDelegateObject fn = (AwaitBoolDelegateObject) required;
                bool ic = fn(awp[ResultRx]);
                if (!ic)
                    awp.Abort();
                else
                    awp.Done();
                return;
            }

            if (awp[ResultRx] != required)
                awp.Abort();
            else
                awp.Done();
        }

        public static bool NotFailOrEmpty(object item)
        {
            if (Type.GetScriptType(item) == "string")
                return !String.IsNullOrEmpty((string) item);
            if (Type.GetScriptType(item) == "boolean")
                return (bool) item;
            return Script.IsValue(item);
        }

        public static bool NotFail(object item)
        {
            if (Type.GetScriptType(item) == "boolean")
                return (bool) item;
            return true;
        }

        private static bool CreateOtherFocus()
        {
            new Await()
                .AddDx(delegate { _otherFocusEl = jQuery.FromHtml("<div id='OtherFocus' tabindex='98989898' style='position:absolute;top:-10000px;left:-10000px;width:1px;height:1px'/>").AppendTo(Document.Body); })
                .Commit();
            return !OthrFocsCrtd; // Makes Resharper and compiler happy
        }

        public static void Unfocus()
        {
            if (_otherFocusEl != null)
                _otherFocusEl.Offset((jQueryPosition) (object) new Dictionary("top", jQuery.Document.GetScrollTop() + 5, "left", - 10000)).Focus();
        }

        public static void Autosize(jQueryObject el)
        {
        }

        public static string GetSelectedText()
        {
            string t = "";
            if ((bool) Script.Literal("window.getSelection"))
            {
                t = (string) Script.Literal("window.getSelection()");
            }
            else if ((bool) Script.Literal("document.getSelection"))
            {
                t = (string) Script.Literal("document.getSelection()");
            }
            else if ((bool) Script.Literal("document.selection"))
            {
                t = (string) Script.Literal("document.selection.createRange().text");
            }
            return (string) Script.Literal("{0}.toString()", t); // Can be a DOMSelection object so needs the .toString()
        }
    }

    public sealed class TargetEvent
    {
        // Alternate view of a jQueryEvent object.
        // Target values are added with DomElement.CalcTargets()
        public bool AltKey;
        public bool CtrlKey;
        public int OffsetX;
        public int OffsetY;
        public int PageX;
        public int PageY;
        public bool ShiftKey;
        public Element Target;
        public int TargetHeight;
        public jQueryPosition TargetOffset;
        public int TargetWidth;
        public double TargetX;
        public double TargetY;
        public string Which;
    }
}